package jbase.project.system.net.http.management;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;

import jbase.project.system.core.Sys;
import jbase.project.system.core.common.Console;
import jbase.project.system.core.common.EventHook;
import jbase.project.system.exception.SysException;
import jbase.project.system.net.http.HttpConstants;
import jbase.project.system.net.http.IHttpHandle;
import jbase.project.system.util.CommonMethod;
import jbase.project.system.util.IOUtil;

public class JavaScriptConsoleHttpHandle implements IHttpHandle, Console {

	private ScriptEngine script = null;
	private boolean isReady = false;
	private EventHook hook;

	public JavaScriptConsoleHttpHandle(EventHook hook) {
		this.hook = hook;
		this.init();
	}

	public void init() {
		if (!this.isReady) {
			this.script = new ScriptEngineManager().getEngineFactories().get(0).getScriptEngine();
			boolean loadServer = this.executeCommand("load", "/Server.js");
			boolean loadEngine = this.executeCommand("load", "/Handlers.js");
			this.isReady = loadServer && loadEngine;
			this.hook.event("out", this, "isReady");
		}
	}

	@SuppressWarnings("unused")
	@Override
	public void handleRequest(Map<String, Object> request) {

		String requestMethod = (String) request.get(HttpConstants.HTTP_REQUEST_FIELD_METHOD);
		String requestHeaders = new String((byte[]) request.get(HttpConstants.HTTP_REQUEST_FIELD_HEADERS));
		String host = (String) request.get(HttpConstants.HTTP_REQUEST_FIELD_HOST);
		String version = (String) request.get(HttpConstants.HTTP_REQUEST_FIELD_VERSION);
		Integer contentLength = (Integer) request.get(HttpConstants.HTTP_REQUEST_FIELD_CONTENT_LENGTH);
		String methodPath = (String) request.get(HttpConstants.HTTP_REQUEST_FIELD_METHOD_PATH);
		String contentType = (String) request.get(HttpConstants.HTTP_REQUEST_FIELD_CONTENT_TYPE);
		byte[] requestBodyBytes = (byte[]) request.get(HttpConstants.HTTP_REQUEST_FIELD_BODY);
		if (requestBodyBytes == null) {
			requestBodyBytes = new byte[] {};
		}
		String requestBody = new String(requestBodyBytes);
		InputStream httpIn = (InputStream) request.get(HttpConstants.HTTP_STREAM_IN);
		OutputStream httpOut = (OutputStream) request.get(HttpConstants.HTTP_STREAM_OUT);

		Invocable invoke = (Invocable) this.script;
		try {
			Map<String, Object> requestContext = new HashMap<String, Object>();
			requestContext.put("request.headers", requestHeaders);
			requestContext.put("request.host", host);
			requestContext.put("request.version", version);
			requestContext.put("request.contentLength", contentLength);
			requestContext.put("request.method", requestMethod);
			requestContext.put("request.contentType", contentType);
			requestContext.put("request.methodPath", methodPath);
			requestContext.put("request.body", requestBody);
			EventHook responseHook = new HttpResponseHook(httpOut);

			// 调试时每次重新加载
			// this.init();

			invoke.invokeFunction("handleRequest", this, requestContext, responseHook);
			responseHook.event("end");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private Map<String, Object> cache = new HashMap<>();

	@Override
	public <T, R> R executeCommand(String command, T... objects) {
		if (command.equals("find")) {
			String relativePath = (String) objects[0];
			if (cache.containsKey(relativePath)) {
				return (R) cache.get(relativePath);
			}
			String content = null;
			try {
				content = IOUtil.readText(
						ManagementServer.class.getResourceAsStream("/resource/http/management" + relativePath));
			} catch (Exception e) {
				content = "";
			}
			return (R) content;
		} else if ("matches".equals(command)) {
			return (R) (Object) ((String) objects[0]).matches((String) objects[1]);
		} else if ("load".equals(command)) {
			String js = null;
			try {
				js = IOUtil.readText(
						ManagementServer.class.getResourceAsStream("/resource/http/management" + (String) objects[0]));
			} catch (Exception e) {
				js = "";
				return (R) (Object) false;
			}
			try {
				this.script.eval(js);
				return (R) (Object) true;
			} catch (ScriptException e) {
				return (R) (Object) false;
			}
		} else if (command.equals("vars.keyset")) {
			Set<String> varKeySet = Sys.getVarKeySet();
			return (R) varKeySet;
		} else if (command.equals("render.velocity")) {
			String template = (String) objects[0];
			Map<String, Object> vectx = (Map) objects[1];
			VelocityEngine ve = new VelocityEngine();
			VelocityContext vec = new VelocityContext(vectx);
			StringWriter writer = new StringWriter();
			ve.evaluate(vec, writer, "my", template);
			return (R) new String(writer.toString());
		} else if (command.equals("create")) {
			Object createdObject = null;
			String clazzName = (String) objects[0];
			Class<?> clazz = null;

			try {
				clazz = Class.forName(clazzName);
			} catch (ClassNotFoundException e) {
				return (R) createdObject;
			}

			int parameterCount = objects.length - 1;
			if (objects.length == 1) {
				try {
					createdObject = clazz.newInstance();
				} catch (Exception e) {
					return (R) createdObject;
				}
			} else {
				Constructor<?>[] constructors = clazz.getConstructors();
				List<Constructor> findConstructors = new ArrayList<Constructor>();
				for (int i = 0; i < constructors.length; i++) {
					Constructor<?> constructor = constructors[i];
					Class<?>[] parameterClazzes = constructor.getParameterTypes();
					int count = 0;
					if (parameterClazzes != null) {
						count = parameterClazzes.length;
					}
					if (parameterCount == count) {
						findConstructors.add(constructor);
					}
				}
				boolean matchedParameter = true;
				boolean created = false;
				if (findConstructors.size() > 0) {
					for (int i = 0; i < findConstructors.size() && !created; i++) {
						Constructor<?> constructor = constructors[i];
						Class<?>[] parameterClazzes = constructor.getParameterTypes();
						Object[] parameters = new Object[parameterCount];

						for (int j = 0; j < parameterCount && !created; j++) {
							try {
								parameters[j] = CommonMethod.getInstance().baseCast(objects[j + 1],
										parameterClazzes[j]);
							} catch (SysException e) {
								matchedParameter = false;
								break;
							}
						}
						if (matchedParameter == true) {
							try {
								createdObject = constructor.newInstance(parameters);
								created = true;
							} catch (Exception e) {
							}
						}
					}
				} else {
					return null;
				}
			}
			if (createdObject != null) {
				String uuid = CommonMethod.getInstance().uuid();
				Sys.setVar(uuid, createdObject);
				return (R) (Object) uuid;
			}
		}
		return null;
	}
}
